﻿using Newtonsoft.Json;
using System.Collections.Generic;
using UnityEngine;
namespace HBFramework
{
    /// <summary>
    /// AStar 地图
    /// </summary>
    public class Map
    {
        public Point[,] data;
        public int width;
        public int height;

        private List<Point> openList;
        private List<Point> closeList;
        private List<int> avoidList;

        public Map(string json)
        {
            int[,] jsonData = JsonConvert.DeserializeObject<int[,]>(json);
            width = jsonData.GetLength(0);
            height = jsonData.GetLength(1);
            data = new Point[width, height];
            for (int i = 0; i < width; i++)
            {
                for (int j = 0; j < height; j++)
                {
                    var tmp = jsonData[i, j];
                    data[i, j] = new Point(i, j, tmp);
                }
            }
        }

        /// <summary>
        /// 获取路径
        /// </summary>
        /// <param name="from"></param>
        /// <param name="to"></param>
        /// <returns></returns>
        public List<Point> GetWay(Vector2Int from, Vector2Int to, List<int> avoidList = null)
        {
            var fromP = data[from.x, from.y];
            var toP = data[to.x, to.y];
            this.avoidList = avoidList;
            return GetWay(fromP, toP);
        }

        /// <summary>
        /// 获取路径
        /// </summary>
        /// <param name="from"></param>
        /// <param name="to"></param>
        /// <returns></returns>
        public List<Point> GetWay(Point from, Point to)
        {
            openList = new List<Point>();
            closeList = new List<Point>();
            openList.Add(from);
            while (openList.Count > 0)
            {
                Point point = FindMinFPoint(openList);
                openList.Remove(point);
                closeList.Add(point);
                List<Point> arroundPoints = GetArroundPoints(point);
                PointFilter(arroundPoints, closeList);
                for (int i = 0; i < arroundPoints.Count; i++)
                {
                    var p = arroundPoints[i];
                    if (openList.Contains(p))
                    {
                        float g = CalcG(p, point);
                        if (g < p.G)
                        {
                            p.UpdateParent(point, g);
                        }
                    }
                    else
                    {
                        p.parent = point;
                        CalcF(p, to);
                        openList.Add(p);
                    }
                }
                if (openList.Contains(to))
                {
                    return GetWay(to);
                }
            }
            return null;
        }

        /// <summary>
        /// 根据终点parent反向路径
        /// </summary>
        /// <param name="point"></param>
        /// <returns></returns>
        private List<Point> GetWay(Point point)
        {
            var end = point;
            List<Point> result = new List<Point>();
            while (end != null)
            {
                result.Insert(0, end);
                end = end.parent;
            }
            return result;
        }

        /// <summary>
        /// 过滤closeList中的点
        /// </summary>
        /// <param name="src"></param>
        /// <param name="closeList"></param>
        private void PointFilter(List<Point> src, List<Point> closeList)
        {
            for (int i = 0; i < src.Count; i++)
            {
                var point = src[i];
                if (closeList.Contains(point))
                {
                    src.RemoveAt(i);
                    i--;
                }
            }
        }

        /// <summary>
        /// 获取四周的点
        /// </summary>
        /// <param name="point"></param>
        /// <returns></returns>
        private List<Point> GetArroundPoints(Point point)
        {
            List<Point> result = new List<Point>();
            if (point.Y < height - 1)
            {
                var up = data[point.X, point.Y + 1];
                result.Add(up);
            }
            if (point.Y > 0)
            {
                var down = data[point.X, point.Y - 1];
                result.Add(down);
            }
            if (point.X < width - 1)
            {
                var right = data[point.X + 1, point.Y];
                result.Add(right);
            }
            if (point.X > 0)
            {
                var left = data[point.X - 1, point.Y];
                result.Add(left);
            }

            //排除避免类型
            if (avoidList != null)
            {
                for (int i = 0; i < result.Count; i++)
                {
                    var p = result[i];
                    if (avoidList.Contains(p.Type))
                    {
                        result.RemoveAt(i);
                        i--;
                    }
                }
            }

            return result;
        }

        /// <summary>
        /// 查找最小F点
        /// </summary>
        /// <param name="openList"></param>
        /// <returns></returns>
        private Point FindMinFPoint(List<Point> openList)
        {
            float f = float.MaxValue;
            Point tmp = null;
            for (int i = 0; i < openList.Count; i++)
            {
                var p = openList[i];
                if (p.F < f)
                {
                    tmp = p;
                    f = p.F;
                }
            }
            return tmp;
        }

        /// <summary>
        /// 计算G
        /// </summary>
        /// <param name="cur"></param>
        /// <param name="parent"></param>
        /// <returns></returns>
        private float CalcG(Point cur, Point parent)
        {
            var x = parent.X - cur.X;
            var y = parent.Y - cur.Y;
            var dis = Mathf.Sqrt(x * x + y * y);
            return dis + parent.G;
        }

        /// <summary>
        /// 计算F
        /// </summary>
        /// <param name="cur"></param>
        /// <param name="end"></param>
        private void CalcF(Point cur, Point end)
        {
            float h = Mathf.Abs(end.X - cur.X) + Mathf.Abs(end.Y - cur.Y);
            float g = 0;
            if (cur.parent != null)
            {
                g = CalcG(cur, cur.parent);
            }
            cur.G = g;
            cur.H = h;
            cur.F = g + h;
        }
    }
}